// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package os

import (
	"internal/poll"
	"io"
	"runtime"
	"syscall"
	"time"
)

// fixLongPath是非Windows平台上的noop。
func fixLongPath(path string) string {
	return path
}

// 文件是*文件的真实表示。
// 额外的间接级别确保操作系统
// 的任何客户端都不能覆盖此数据，这可能会导致终结器
// 关闭错误的文件描述符。
type file struct {
	fd         int
	name       string
	dirinfo    *dirInfo // nil除非正在读取的目录
	appendMode bool     // 是否打开文件以追加
}

// Fd返回引用打开文件的整数计划9文件描述符。
// 如果关闭f，文件描述符将无效。
// 如果f被垃圾收集，终结器可能会关闭文件描述符，
// 使其无效；请参阅运行时。SetFinalizer获取有关何时运行
// 终结器的更多信息。在Unix系统上，这将导致SetDeadline 
// 方法停止工作。
// 
// 作为替代方法，请参见f.SyscallConn方法。
func (f *File) Fd() uintptr {
	if f == nil {
		return ^(uintptr(0))
	}
	return uintptr(f.fd)
}

// NewFile返回一个具有给定文件描述符和
// 名称的新文件。如果fd不是有效的文件
// 描述符，则返回值为零。
func NewFile(fd uintptr, name string) *File {
	fdi := int(fd)
	if fdi < 0 {
		return nil
	}
	f := &File{&file{fd: fdi, name: name}}
	runtime.SetFinalizer(f.file, (*file).close)
	return f
}

// 辅助信息，如果文件描述了目录
type dirInfo struct {
	buf  [syscall.STATMAX]byte // 目录I/O缓冲区
	nbuf int                   // buf的长度；从读取
	bufp int                   // 返回值buf中下一条记录的位置。
}

func epipecheck(file *File, e error) {
}

// DevNull是操作系统的“空设备”的名称
// 在类Unix系统上，它是“/dev/null”；在Windows上，“NUL”。
const DevNull = "/dev/null"

// syscallMode从Go的便携模式位返回系统调用特定的模式位。
func syscallMode(i FileMode) (o uint32) {
	o |= uint32(i.Perm())
	if i&ModeAppend != 0 {
		o |= syscall.DMAPPEND
	}
	if i&ModeExclusive != 0 {
		o |= syscall.DMEXCL
	}
	if i&ModeTemporary != 0 {
		o |= syscall.DMTMP
	}
	return
}

// openFileNolog是OpenFile的Plan 9实现。
func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
	var (
		fd     int
		e      error
		create bool
		excl   bool
		trunc  bool
		append bool
	)

	if flag&O_CREATE == O_CREATE {
		flag = flag & ^O_CREATE
		create = true
	}
	if flag&O_EXCL == O_EXCL {
		excl = true
	}
	if flag&O_TRUNC == O_TRUNC {
		trunc = true
	}
	// O_APPEND在Plan 9上被模拟
	if flag&O_APPEND == O_APPEND {
		flag = flag &^ O_APPEND
		append = true
	}

	if (create && trunc) || excl {
		fd, e = syscall.Create(name, flag, syscallMode(perm))
	} else {
		fd, e = syscall.Open(name, flag)
		if IsNotExist(e) && create {
			fd, e = syscall.Create(name, flag, syscallMode(perm))
			if e != nil {
				return nil, &PathError{Op: "create", Path: name, Err: e}
			}
		}
	}

	if e != nil {
		return nil, &PathError{Op: "open", Path: name, Err: e}
	}

	if append {
		if _, e = syscall.Seek(fd, 0, io.SeekEnd); e != nil {
			return nil, &PathError{Op: "seek", Path: name, Err: e}
		}
	}

	return NewFile(uintptr(fd), name), nil
}

// Close关闭文件，使其无法用于I/O。
// 在支持SetDeadline的文件上，任何挂起的I/O操作都将被取消，并立即返回ErrClosed错误。
// Close如果已经被调用，将返回一个错误。
func (f *File) Close() error {
	if err := f.checkValid("close"); err != nil {
		return err
	}
	return f.file.close()
}

func (file *file) close() error {
	if file == nil || file.fd == badFd {
		return ErrInvalid
	}
	var err error
	if e := syscall.Close(file.fd); e != nil {
		err = &PathError{Op: "close", Path: file.name, Err: e}
	}
	file.fd = badFd // 因此它不能再次关闭

	// 不再需要终结器
	runtime.SetFinalizer(file, nil)
	return err
}

// Stat返回描述文件的FileInfo结构。
// 如果有错误，则类型为*PathError。
func (f *File) Stat() (FileInfo, error) {
	if f == nil {
		return nil, ErrInvalid
	}
	d, err := dirstat(f)
	if err != nil {
		return nil, err
	}
	return fileInfoFromStat(d), nil
}

// Truncate更改文件大小。
// 它不会更改I/O偏移量。
// 如果有错误，则类型为*PathError。
func (f *File) Truncate(size int64) error {
	if f == nil {
		return ErrInvalid
	}

	var d syscall.Dir
	d.Null()
	d.Length = size

	var buf [syscall.STATFIXLEN]byte
	n, err := d.Marshal(buf[:])
	if err != nil {
		return &PathError{Op: "truncate", Path: f.name, Err: err}
	}
	if err = syscall.Fwstat(f.fd, buf[:n]); err != nil {
		return &PathError{Op: "truncate", Path: f.name, Err: err}
	}
	return nil
}

const chmodMask = uint32(syscall.DMAPPEND | syscall.DMEXCL | syscall.DMTMP | ModePerm)

func (f *File) chmod(mode FileMode) error {
	if f == nil {
		return ErrInvalid
	}
	var d syscall.Dir

	odir, e := dirstat(f)
	if e != nil {
		return &PathError{Op: "chmod", Path: f.name, Err: e}
	}
	d.Null()
	d.Mode = odir.Mode&^chmodMask | syscallMode(mode)&chmodMask

	var buf [syscall.STATFIXLEN]byte
	n, err := d.Marshal(buf[:])
	if err != nil {
		return &PathError{Op: "chmod", Path: f.name, Err: err}
	}
	if err = syscall.Fwstat(f.fd, buf[:n]); err != nil {
		return &PathError{Op: "chmod", Path: f.name, Err: err}
	}
	return nil
}

// Sync将文件的当前内容提交到稳定存储。
// 通常，这意味着将文件系统最近写入的数据的内存副本刷新到磁盘。
func (f *File) Sync() error {
	if f == nil {
		return ErrInvalid
	}
	var d syscall.Dir
	d.Null()

	var buf [syscall.STATFIXLEN]byte
	n, err := d.Marshal(buf[:])
	if err != nil {
		return &PathError{Op: "sync", Path: f.name, Err: err}
	}
	if err = syscall.Fwstat(f.fd, buf[:n]); err != nil {
		return &PathError{Op: "sync", Path: f.name, Err: err}
	}
	return nil
}

// 读取从文件中最多读取len（b）字节。
// 返回读取的字节数和错误（如果有）。
func (f *File) read(b []byte) (n int, err error) {
	n, e := fixCount(syscall.Read(f.fd, b))
	if n == 0 && len(b) > 0 && e == nil {
		return 0, io.EOF
	}
	return n, e
}

// pread从文件中读取len（b）字节，从字节偏移量off开始。
// 返回读取的字节数和错误（如果有）。
// EOF由一个错误设置为零的零计数发出信号。
func (f *File) pread(b []byte, off int64) (n int, err error) {
	n, e := fixCount(syscall.Pread(f.fd, b, off))
	if n == 0 && len(b) > 0 && e == nil {
		return 0, io.EOF
	}
	return n, e
}

// write将len（b）字节写入文件。
// 返回写入的字节数和错误（如果有）。
// 由于计划9保留了消息边界，因此绝不允许
// 零字节写入。
func (f *File) write(b []byte) (n int, err error) {
	if len(b) == 0 {
		return 0, nil
	}
	return fixCount(syscall.Write(f.fd, b))
}

// pwrite从字节偏移量off开始将len（b）字节写入文件。
// 返回写入的字节数和错误（如果有）。
// 由于计划9保留消息边界，因此绝不允许
// 零字节写入。
func (f *File) pwrite(b []byte, off int64) (n int, err error) {
	if len(b) == 0 {
		return 0, nil
	}
	return fixCount(syscall.Pwrite(f.fd, b, off))
}

// seek将下一次读取或写入文件的偏移量设置为偏移量，解释
// 根据其位置：0表示相对于文件原点，1表示相对于当前偏移量，2表示相对于结束。
// 返回新的偏移量和错误（如果有）。
func (f *File) seek(offset int64, whence int) (ret int64, err error) {
	if f.dirinfo != nil {
		// 释放缓存的dirinfo，因此如果我们再次将此文件作为目录访问，我们将分配一个新的dirinfo。参见35767和35161。
		f.dirinfo = nil
	}
	return syscall.Seek(f.fd, offset, whence)
}

// Truncate更改命名文件的大小。
// 如果文件是符号链接，它会更改链接目标的大小。
// 如果有错误，则类型为*PathError。
func Truncate(name string, size int64) error {
	var d syscall.Dir

	d.Null()
	d.Length = size

	var buf [syscall.STATFIXLEN]byte
	n, err := d.Marshal(buf[:])
	if err != nil {
		return &PathError{Op: "truncate", Path: name, Err: err}
	}
	if err = syscall.Wstat(name, buf[:n]); err != nil {
		return &PathError{Op: "truncate", Path: name, Err: err}
	}
	return nil
}

// 删除删除指定的文件或目录。
// 如果有错误，则类型为*PathError。
func Remove(name string) error {
	if e := syscall.Remove(name); e != nil {
		return &PathError{Op: "remove", Path: name, Err: e}
	}
	return nil
}

// HasPrefix来自strings包。
func hasPrefix(s, prefix string) bool {
	return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
}

func rename(oldname, newname string) error {
	dirname := oldname[:lastIndex(oldname, '/')+1]
	if hasPrefix(newname, dirname) {
		newname = newname[len(dirname):]
	} else {
		return &LinkError{"rename", oldname, newname, ErrInvalid}
	}

	// 如果在删除旧名称
	// 前缀后newname仍然包含斜杠，则重命名是跨目录的，必须拒绝。
	if lastIndex(newname, '/') >= 0 {
		return &LinkError{"rename", oldname, newname, ErrInvalid}
	}

	var d syscall.Dir

	d.Null()
	d.Name = newname

	buf := make([]byte, syscall.STATFIXLEN+len(d.Name))
	n, err := d.Marshal(buf[:])
	if err != nil {
		return &LinkError{"rename", oldname, newname, err}
	}

	// 如果newname已经存在并且不是目录，则重命名将替换它。
	f, err := Stat(dirname + newname)
	if err == nil && !f.IsDir() {
		Remove(dirname + newname)
	}

	if err = syscall.Wstat(oldname, buf[:n]); err != nil {
		return &LinkError{"rename", oldname, newname, err}
	}
	return nil
}

// 查看文件中的文档。开始：Chmod。
func chmod(name string, mode FileMode) error {
	var d syscall.Dir

	odir, e := dirstat(name)
	if e != nil {
		return &PathError{Op: "chmod", Path: name, Err: e}
	}
	d.Null()
	d.Mode = odir.Mode&^chmodMask | syscallMode(mode)&chmodMask

	var buf [syscall.STATFIXLEN]byte
	n, err := d.Marshal(buf[:])
	if err != nil {
		return &PathError{Op: "chmod", Path: name, Err: err}
	}
	if err = syscall.Wstat(name, buf[:n]); err != nil {
		return &PathError{Op: "chmod", Path: name, Err: err}
	}
	return nil
}

// Chtimes更改命名
// 文件的访问和修改时间，类似于Unix utime（）或utimes（）函数。
// 
// 底层文件系统可能会将值截断或四舍五入到
// 不太精确的时间单位。
// 如果有错误，则类型为*PathError。
func Chtimes(name string, atime time.Time, mtime time.Time) error {
	var d syscall.Dir

	d.Null()
	d.Atime = uint32(atime.Unix())
	d.Mtime = uint32(mtime.Unix())

	var buf [syscall.STATFIXLEN]byte
	n, err := d.Marshal(buf[:])
	if err != nil {
		return &PathError{Op: "chtimes", Path: name, Err: err}
	}
	if err = syscall.Wstat(name, buf[:n]); err != nil {
		return &PathError{Op: "chtimes", Path: name, Err: err}
	}
	return nil
}

// 管道返回一对连接的文件；从r中读取返回字节
// 写入w。它返回文件和错误（如果有）。
func Pipe() (r *File, w *File, err error) {
	var p [2]int

	if e := syscall.Pipe(p[0:]); e != nil {
		return nil, nil, NewSyscallError("pipe", e)
	}

	return NewFile(uintptr(p[0]), "|0"), NewFile(uintptr(p[1]), "|1"), nil
}

// 计划9不支持

// Link创建新名称作为指向旧名称文件的硬链接。
// 如果有错误，它将是*LinkError类型。
func Link(oldname, newname string) error {
	return &LinkError{"link", oldname, newname, syscall.EPLAN9}
}

// Symlink创建newname作为指向oldname的符号链接。
// 在Windows上，指向不存在的旧名称的符号链接会创建一个文件符号链接；
// 如果以后将oldname创建为目录，则符号链接将不起作用。
// 如果有错误，它将是*LinkError类型。
func Symlink(oldname, newname string) error {
	return &LinkError{"symlink", oldname, newname, syscall.EPLAN9}
}

// Readlink返回指定符号链接的目标。
// 如果有错误，则类型为*PathError。
func Readlink(name string) (string, error) {
	return "", &PathError{Op: "readlink", Path: name, Err: syscall.EPLAN9}
}

// Chown更改命名文件的数字uid和gid。
// 如果文件是符号链接，它会更改链接目标的uid和gid。
// uid或gid为-1表示不更改该值。
// 如果有错误，则类型为*PathError。在Windows或Plan 9上，Chown总是返回系统调用。EWINDOWS或
// EPLAN9错误，用*路径错误包装。
func Chown(name string, uid, gid int) error {
	return &PathError{Op: "chown", Path: name, Err: syscall.EPLAN9}
}

// Lchown更改命名文件的数字uid和gid。
// 如果文件是符号链接，它会更改链接本身的uid和gid。
// 如果有错误，则类型为*PathError。
func Lchown(name string, uid, gid int) error {
	return &PathError{Op: "lchown", Path: name, Err: syscall.EPLAN9}
}

// Chown更改命名文件的数字uid和gid。wen jian defg
func (f *File) Chown(uid, gid int) error {
	if f == nil {
		return ErrInvalid
	}
	return &PathError{Op: "chown", Path: f.name, Err: syscall.EPLAN9}
}

func tempDir() string {
	dir := Getenv("TMPDIR")
	if dir == "" {
		dir = "/tmp"
	}
	return dir

}

// Chdir将当前工作目录更改为文件
// 该文件必须是一个目录。
// 如果有错误，它将是*PathError类型。
func (f *File) Chdir() error {
	if err := f.checkValid("chdir"); err != nil {
		return err
	}
	if e := syscall.Fchdir(f.fd); e != nil {
		return &PathError{Op: "chdir", Path: f.name, Err: e}
	}
	return nil
}

// 设置读写截止日期。
func (f *File) setDeadline(time.Time) error {
	if err := f.checkValid("SetDeadline"); err != nil {
		return err
	}
	return poll.ErrNoDeadline
}

// 设置读取截止日期设置读取截止日期。
func (f *File) setReadDeadline(time.Time) error {
	if err := f.checkValid("SetReadDeadline"); err != nil {
		return err
	}
	return poll.ErrNoDeadline
}

// setWriteDadline设置写入截止日期。
func (f *File) setWriteDeadline(time.Time) error {
	if err := f.checkValid("SetWriteDeadline"); err != nil {
		return err
	}
	return poll.ErrNoDeadline
}

// checkValid检查f是否有效使用。
// 如果不是，则返回一个适当的错误，可能包含操作名op.
func (f *File) checkValid(op string) error {
	if f == nil {
		return ErrInvalid
	}
	if f.fd == badFd {
		return &PathError{Op: op, Path: f.name, Err: ErrClosed}
	}
	return nil
}

type rawConn struct{}

func (c *rawConn) Control(f func(uintptr)) error {
	return syscall.EPLAN9
}

func (c *rawConn) Read(f func(uintptr) bool) error {
	return syscall.EPLAN9
}

func (c *rawConn) Write(f func(uintptr) bool) error {
	return syscall.EPLAN9
}

func newRawConn(file *File) (*rawConn, error) {
	return nil, syscall.EPLAN9
}

func ignoringEINTR(fn func() error) error {
	return fn()
}
